//
//  TTProbabilityBasedAlgorithm.m
//  iBeaconDemo
//
//  Created by cry on 17/1/11.
//  Copyright © 2017年 eGova. All rights reserved.
//

#import "TTProbabilityBasedAlgorithm.h"
#import "TTProbabilityMapEmpiric.h"
#import "TTPointProbabilityMap.h"
#import "TTPointRoomMap.h"
#import "TTFastConvexHull.h"
#import "TTRoomMap.h"
#import "TTFilter.h"
#import "TTReceiver.h"
#import "TTLine.h"

@interface TTProbabilityBasedAlgorithm()

//坐标范围的粒度划分
//private double granularityRoommap;
@property (nonatomic, assign) double granularityRoommap;
//可能的点集合（包含了坐标、和当前坐标点的计算rssi,key对应receiverID）
//private HashMap<String, ArrayList<PointProbabilityMap>> pointsProbabilityMaps;
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSMutableArray<TTPointProbabilityMap *> *> *pointsProbabilityMaps;
//可能的坐标点（按照粒度划分），带有权值
//private ArrayList<PointRoomMap> pointsRoomMap = new ArrayList<PointRoomMap>();
@property (nonatomic, strong) NSMutableArray<TTPointRoomMap *> *pointsRoomMap;
//过滤器
//private Filter filter;
@property (nonatomic, strong) TTFilter *filter;

@end
//private static final String TAG = ProbabilityBasedAlgorithm.class.getSimpleName();
static NSString *const TAG = @"TTProbabilityBasedAlgorithm";
//默认的坐标范围内的粒度划分值 单位 m
//public static final double GRANULARITY_ROOMMAP_DEFAULT = 0.1;
static double const GRANULARITY_ROOMMAP_DEFAULT = 1;
//此值经测试得大于1
//public static final double FACTOR_FOR_WEIGHTING_ROOMMAP = 5.0;
static double const FACTOR_FOR_WEIGHTING_ROOMMAP = 5.0;

@implementation TTProbabilityBasedAlgorithm

- (NSMutableArray<TTPointRoomMap *> *)pointsRoomMap{
    if (_pointsRoomMap == nil) {
        _pointsRoomMap = [NSMutableArray array];
    }
    return _pointsRoomMap;
}

/**
 * Sets the room map.
 *
 * @param roommap the new room map for the algorithm
 * @param granularity the new granularity for the room map
 */
//public void setRoomMap(RoomMap roommap, double granularity) {
//    this.roommap = roommap;
//    this.granularityRoommap = granularity;
//}
- (void)setupRoomMap:(TTRoomMap *)roomMap
         granularity:(double)granularity{
    self.roommap = roomMap;
    self.granularityRoommap = granularity;
}

/**
 * 初始化可能坐标点的值，为演算做准备
 * @param roommap
 * @param receivers
 * @param xFrom
 * @param xTo
 * @param yFrom
 * @param yTo
 */
//public ProbabilityBasedAlgorithm(RoomMap roommap, ArrayList<Receiver> receivers,double xFrom,double xTo,double yFrom,double yTo,int A,double n, Filter filter) {
//    super(roommap, receivers);
//    this.granularityRoommap = ProbabilityBasedAlgorithm.GRANULARITY_ROOMMAP_DEFAULT;
//    this.filter = filter;
//    pointsProbabilityMaps = new HashMap<String, ArrayList<PointProbabilityMap>>();
//    for (int i = 0; i < receivers.size(); i++) {
//        setProbabilityMapForReceiver(receivers.get(i), n, A,xFrom, xTo, yFrom, yTo,granularityRoommap);
//    }
//}
- (instancetype)initWithRoomMap:(TTRoomMap *)roommap
                      receivers:(NSMutableArray<TTReceiver *> *)receivers
                          xFrom:(double)xFrom
                            xTo:(double)xTo
                          yFrom:(double)yFrom
                            yTo:(double)yTo
                              a:(NSInteger)a
                              n:(double)n
                         filter:(TTFilter *)filter{
    if (self = [super initWithReceivers:receivers roommap:roommap]) {
        self.granularityRoommap = GRANULARITY_ROOMMAP_DEFAULT;
        self.filter = filter;
        self.pointsProbabilityMaps = [NSMutableDictionary dictionary];
        for (NSInteger i = 0; i < receivers.count; i++) {
            [self setProbabilityMapForReceiver:receivers[i] signalPropagationConstant:n signalStrengthOneMeter:a xFrom:xFrom xTo:xTo yFrom:yFrom yTo:yTo granularity:self.granularityRoommap];
        }
    }
    return self;
}

/**
 * 在可能范围内，为每一个信号节点构造可能的目标点坐标集合
 *
 * @param signalPropagationConstant 信号指数常量
 * @param signalStrengthOneMeter 距离信号点1米的abs(rssi)
 * @param xFrom X轴坐标起点
 * @param xTo X轴坐标终点
 * @param yFrom Y轴坐标起点
 * @param yTo Y轴坐标终点
 * @param granularity 划分粒度
 */
//public void setProbabilityMapForReceiver(Receiver receiver, double signalPropagationConstant, int signalStrengthOneMeter,
//                                         double xFrom, double xTo, double yFrom, double yTo, double granularity) {
//    if (getReceiver(this.receivers, receiver.getID()) == null) {
//        this.receivers.add(receiver);
//    }
//    String receiverId = receiver.getID();
//    ArrayList<PointProbabilityMap> pointsProbabilityMap =
//				new ProbabilityMapEmpiric(signalPropagationConstant, signalStrengthOneMeter).getProbabilityMap(xFrom, xTo, yFrom, yTo, granularity,receiver.getXPos(),receiver.getYPos());
//    pointsProbabilityMaps.put(receiverId, pointsProbabilityMap);
//}
- (void)setProbabilityMapForReceiver:(TTReceiver *)receiver
           signalPropagationConstant:(double)signalPropagationConstant
              signalStrengthOneMeter:(NSInteger)signalStrengthOneMeter
                               xFrom:(double)xFrom
                                 xTo:(double)xTo
                               yFrom:(double)yFrom
                                 yTo:(double)yTo
                         granularity:(double)granularity{
    
    if ([self getReceiverWithReceivers:self.receivers uuid:receiver.keyID] == nil) {
        [self.receivers addObject:receiver];
    }
    NSString *receiverKeyId = receiver.keyID;
    NSMutableArray<TTPointProbabilityMap *> *pointsProbabilityMap = [[[TTProbabilityMapEmpiric alloc] initWithN:signalPropagationConstant andA:signalStrengthOneMeter] getProbabilityMapWithXFrom:xFrom xTo:xTo yFrom:yFrom yTo:yTo granularity:granularity x:receiver.xPos y:receiver.yPos];
    
    [self.pointsProbabilityMaps setObject:pointsProbabilityMap forKey:receiverKeyId];
}

/**
 * 根据信号节点和未知点获取的rssi，计算未知点的坐标
 *
 * @param readings APP收到receivers的rssi集合
 * @return 未知点的位置坐标
 */
//@Override
//public Point calculate(HashMap<String, Integer> readings) {

- (TTPoint *)calculate:(NSMutableDictionary<NSString *,NSNumber *> *)readings{
    
//    this.pointsRoomMap = createRoomMap(this.roommap.getxFrom(), this.roommap.getxTo(),
//                                       this.roommap.getyFrom(), this.roommap.getyTo(),
//                                       this.granularityRoommap);
    self.pointsRoomMap = [self createRoomMapWithXFrom:self.roommap.xFrom
                                                  xTo:self.roommap.xTo
                                                yFrom:self.roommap.yFrom
                                                  yTo:self.roommap.yTo
                                          granularity:self.granularityRoommap];
    
//    for (Map.Entry<String, Integer> e : readings.entrySet()) {
    for (NSString *key in readings) {
        //根据receiverID在可能范围内筛选坐标点
        //        ArrayList<PointProbabilityMap> pointsProbabilityMap = pointsProbabilityMaps.get(e.getKey());
        //        if (pointsProbabilityMap == null) {
        //            Log.e(TAG,"未找到对应的receiver");
        //            return null;
        //        }
        NSMutableArray<TTPointProbabilityMap *> *pointsProbabilityMap = self.pointsProbabilityMaps[key];
        if (pointsProbabilityMap == nil) {
            NSLog(@"Receiver not found! class:%@ line:176",NSStringFromClass(self.class));
            return nil;
        }
        // find the points in the probability map where the rssi value is below the given value
        //        ArrayList<PointProbabilityMap> newPointsProbabilityMap = findValuesAboveRssi(pointsProbabilityMap, e.getValue());
        //        if (newPointsProbabilityMap.size() <= 2) {
        //            Log.e(TAG,"在可能的范围内，满足演算的点少于3个");
        //            return null;
        //        }
        NSMutableArray<TTPointProbabilityMap *> *newPointsProbabilityMap = [self findValuesAboveRssi:pointsProbabilityMap rssi:[readings[key] doubleValue]];
        if (newPointsProbabilityMap.count <= 2) {
            NSLog(@"Point less than 3! class:%@ line:185",NSStringFromClass(self.class));
            return nil;
        }
        //        FastConvexHull gs = new FastConvexHull();
        //        ArrayList<PointProbabilityMap> convexHull = gs.computeHull(newPointsProbabilityMap);
        //        Receiver receiver = getReceiver(receivers, e.getKey());
        //        if (receiver == null) {
        //            Log.e(TAG,"在receivers没有找到对应的receiverid");
        //            return null;
        //        }
        TTFastConvexHull *gs = [[TTFastConvexHull alloc] init];
        NSMutableArray<TTPointProbabilityMap *> *convexHull = [gs computeHull:newPointsProbabilityMap];
        TTReceiver *receiver = [self getReceiverWithReceivers:self.receivers uuid:key];
        if (receiver == nil) {
            NSLog(@"No one with receoverID is inclueded in receivers! class:%@ line:200",NSStringFromClass(self.class));
            return nil;
        }
     
        //        ArrayList<PointProbabilityMap> convexHullTransformed =
        //        convexHullTransformation(convexHull, 0);
        //        weightRoomMap(this.pointsRoomMap, convexHullTransformed);
        //    }
        NSMutableArray<TTPointProbabilityMap *> *convexHullTransformed = [self convexHullTransformation:convexHull angle:0];
        [self weightRoomMap:self.pointsRoomMap convexHull:convexHullTransformed];
    }

//    ArrayList<PointRoomMap> highestPointsRoomMap = giveMaxWeightedValue(this.pointsRoomMap);
//    Point p = getPosition(highestPointsRoomMap);
//    // calculate point
//    Log.e(TAG,"[" + p.getX() + ";" + p.getY() + "]");
//    p = this.filter.applyFilter(p);
//    Log.e(TAG,"重算后：[" + p.getX() + ";" + p.getY() + "]");
//    return p;
//}
    NSMutableArray<TTPointRoomMap *> *highestPointsRoomMap = [self giveMaxWeightedValue:self.pointsRoomMap];
    TTPoint *p = [self getPositionWithRoomMap:highestPointsRoomMap];
    p = [self.filter applyFilterWithPiont:p];
    return p;
}

/**
 * Each point of the room map will be tested if it is located inside the given convex hull or not. If the point is located inside the
 * convex hull, the weight of the point will be multiplied by FACTOR_FOR_WEIGHTING_ROOMMAP.
 *
 * @param roomMap the points of the room map
 * @param convexHull the points of the convex hull
 */
//private void weightRoomMap(ArrayList<PointRoomMap> roomMap, ArrayList<PointProbabilityMap> convexHull) {
//    for (int i = 0; i < roomMap.size(); i++) {
//        if (liesPointInConvexHull(roomMap.get(i), convexHull)) {
//            roomMap.get(i).setNewWeightValue(roomMap.get(i).getWeightValue() * ProbabilityBasedAlgorithm.FACTOR_FOR_WEIGHTING_ROOMMAP);
//        }
//    }
//}
- (void)weightRoomMap:(NSMutableArray<TTPointRoomMap *> *)roomMap
           convexHull:(NSMutableArray<TTPointProbabilityMap *> *)convexHull{
    for (NSInteger i = 0; i < roomMap.count; i++) {
        if ([self liesPointInConvexHullWithPointRoomMap:roomMap[i] convexHull:convexHull]) {
            [roomMap[i] setNewWeightValue:roomMap[i].weightValue * FACTOR_FOR_WEIGHTING_ROOMMAP];
        }
    }
}
/**
 * 测试给定的点是否在凸包内
 *
 * @param pRoomMap 带权值的做坐标点
 * @param convexHull the convex hull
 * @return true, if point is located inside the convex hull, false otherwise
 */
//private boolean liesPointInConvexHull(PointRoomMap pRoomMap, ArrayList<PointProbabilityMap> convexHull) {
//    int size = convexHull.size();
//    for (int i = 0; i < size; i++) {
//        Point p1 = convexHull.get(i % size);
//        Point p2 = convexHull.get((i + 1) % size);
//        Line l = new Line(p1, p2);
//        
//        if (pRoomMap.isRightOf(l)) {
//            return false;
//        }
//    }
//    return true;
//}

- (BOOL)liesPointInConvexHullWithPointRoomMap:(TTPointRoomMap *)pRoomMap
                                   convexHull:(NSMutableArray<TTPointProbabilityMap *> *)convexHull{
    
    NSInteger size = convexHull.count;
    for (NSInteger i = 0; i < size; i++) {
        TTPoint *p1 = convexHull[i % size];
        TTPoint *p2 = convexHull[(i+1) % size];
        TTLine *l = [[TTLine alloc] initWithStartPoint:p1 endPoint:p2];
        
        if ([pRoomMap isRightOfLine:l]) {
            return NO;
        }
    }
    return YES;
}

/**
 * Goes through the given probability map and filters all values that are above the given rssi. A new List of possible points
 * will be returned then.
 *
 * @param probabilityMap the probability map that shall be filtered
 * @param rssi the rssi value
 * @return an array list of possible points
 */
//private ArrayList<PointProbabilityMap> findValuesAboveRssi(ArrayList<PointProbabilityMap> probabilityMap, double rssi) {
//    ArrayList<PointProbabilityMap> pMap = new ArrayList<PointProbabilityMap>();
//    for (int i = 0; i < probabilityMap.size(); i++) {
//        if (probabilityMap.get(i).getRSSIValue() >= rssi) {
//            pMap.add(probabilityMap.get(i));
//        }
//    }
//    return pMap;
//}
- (NSMutableArray<TTPointProbabilityMap *> *)findValuesAboveRssi:(NSMutableArray<TTPointProbabilityMap *> *)probabilityMap rssi:(double)rssi{
    
    NSMutableArray<TTPointProbabilityMap *> *pMap = [NSMutableArray array];
    for (NSInteger i = 0; i < probabilityMap.count; i++) {
        if (probabilityMap[i].rssiValue >= rssi) {
            [pMap addObject:probabilityMap[i]];
        }
    }
    return pMap;
}
/**
 * Creates an array list of the room map with weighted points ({@link algorithm.helper.PointRoomMap}), depending on the given parameters.
 *
 * @param xFrom X轴起点
 * @param xTo X轴终点
 * @param yFrom Y轴起点
 * @param yTo Y轴终点
 * @param granularity 坐标划分粒度
 * @return an array list that represents the weighted room map
 */
//private ArrayList<PointRoomMap> createRoomMap(double xFrom, double xTo, double yFrom, double yTo, double granularity) {
//    ArrayList<PointRoomMap> roomMap = new ArrayList<PointRoomMap>();
//    for (double i = xFrom; i <= xTo; i += granularity) { // x-Axe
//        for (double j = yFrom; j <= yTo; j += granularity) { // y-Axe
//            roomMap.add(new PointRoomMap(i, j));
//        }
//    }
//    return roomMap;
//}
- (NSMutableArray<TTPointRoomMap *> *)createRoomMapWithXFrom:(double)xFrom
                                                         xTo:(double)xTo
                                                       yFrom:(double)yFrom
                                                         yTo:(double)yTo
                                                 granularity:(double)granularity{
    NSMutableArray<TTPointRoomMap *> *roomMap = [NSMutableArray array];
    for (double i = xFrom; i <= xTo; i+= granularity) {
        for (double j = yFrom; j <= yTo; j += granularity) {
            [roomMap addObject:[[TTPointRoomMap alloc] initWithX:i Y:j]];
        }
    }
    return roomMap;
}

/**
 * 计算roomMap中可能点的平均值，作为最合适的点
 *
 * @param roomMap the points of the room map with the highest weighted values
 * @return the average position as a point
 */
//private Point getPosition(ArrayList<PointRoomMap> roomMap) {
//    double sumX = 0, sumY = 0;
//    for (int i = 0; i < roomMap.size(); i++) {
//        sumX += roomMap.get(i).getX();
//        sumY += roomMap.get(i).getY();
//    }
//    double x = sumX / roomMap.size();
//    double y = sumY / roomMap.size();
//    
//    return new Point(x, y);
//}
- (TTPoint *)getPositionWithRoomMap:(NSMutableArray<TTPointRoomMap *> *)roomMap{
    double sumX = 0, sumY = 0;
    for (NSInteger i = 0; i < roomMap.count; i++) {
        sumX += roomMap[i].x;
        sumY += roomMap[i].y;
    }
    double x = sumX / roomMap.count;
    double y = sumY / roomMap.count;
    return [[TTPoint alloc] initWithX:x Y:y];
}

/**
 * 根据id查找receiver
 * @param receivers
 * @param id
 * @return
 */
//private Receiver getReceiver(ArrayList<Receiver> receivers, String id) {
//    for (int i = 0; i < receivers.size(); i++) {
//        if (receivers.get(i).getID().equals(id)) {
//            return receivers.get(i);
//        }
//    }
//    return null;
//}
- (TTReceiver *)getReceiverWithReceivers:(NSMutableArray<TTReceiver *> *)receivers
                                    uuid:(NSString *)uuid{
    for (int i = 0; i < receivers.count; i++) {
        if ([uuid isEqualToString:receivers[i].keyID]) {
            return receivers[i];
        }
    }
    return nil;
}

/**
 * 根据角度重新构造凸多边形坐标
 *
 * @param points the points that shall be transformed
 * @param angle the angle for the transformation
 * @return the transformed convex hull
 */
//private ArrayList<PointProbabilityMap> convexHullTransformation(ArrayList<PointProbabilityMap> points, double angle) {
    // Define a new List for the transformed coordinates
//    ArrayList<PointProbabilityMap> transformationProbMap = new ArrayList<PointProbabilityMap>();

    // Rotation of the coordinates
//    for (int i = 0; i < points.size(); i++) {
//        double xRotation = Math.cos(angle) * points.get(i).getX() - Math.sin(angle) * points.get(i).getY();
//        double yRotation = Math.sin(angle) * points.get(i).getX() + Math.cos(angle) * points.get(i).getY();
//        transformationProbMap.add(new PointProbabilityMap(xRotation, yRotation, points.get(i).getRSSIValue()));
//    }
//    return transformationProbMap;
//}
- (NSMutableArray<TTPointProbabilityMap *> *)convexHullTransformation:(NSMutableArray<TTPointProbabilityMap *> *)points
                                                                angle:(double)angle{
    NSMutableArray<TTPointProbabilityMap *> *transformationProbMap = [NSMutableArray array];
    
    for (NSInteger i = 0; i < points.count; i++) {
        
        double xRotation = cos(angle) * points[i].x - sin(angle) * points[i].y;
        double yRotation = sin(angle) * points[i].x + cos(angle) * points[i].y;
        [transformationProbMap addObject:[[TTPointProbabilityMap alloc] initWithX:xRotation Y:yRotation rssiValue:points[i].rssiValue]];
    }
    return transformationProbMap;
}

/**
 * Searches for the highest weighted points in the room map.
 *
 * @param roomMap the room map to search for the highest weighted points
 * @return a array list with the highest weighted points
 */
//private ArrayList<PointRoomMap> giveMaxWeightedValue(ArrayList<PointRoomMap> roomMap) {
//    ArrayList<PointRoomMap> maxWeightedValue = new ArrayList<PointRoomMap>();
//    double maxValue = roomMap.get(0).getWeightValue();
//    
//    for (int i = 1; i < roomMap.size(); i++) {
//        if (roomMap.get(i).getWeightValue() > maxValue) {
//            maxValue = roomMap.get(i).getWeightValue();
//            maxWeightedValue.clear();
//            maxWeightedValue.add(roomMap.get(i));
//            
//        } else if (roomMap.get(i).getWeightValue() == maxValue) {
//            maxWeightedValue.add(roomMap.get(i));
//        }		
//    }
//    return maxWeightedValue;
//}
- (NSMutableArray<TTPointRoomMap *> *)giveMaxWeightedValue:(NSMutableArray<TTPointRoomMap *> *)roomMap{

    NSMutableArray<TTPointRoomMap *> *maxWeightedValue = [NSMutableArray array];
    double maxValue = roomMap[0].weightValue;
    
    for (NSInteger i = 0; i < roomMap.count; i++) {
        if (roomMap[i].weightValue > maxValue) {
            maxValue = roomMap[i].weightValue;
            [maxWeightedValue removeAllObjects];
            [maxWeightedValue addObject:roomMap[i]];
        }else if (roomMap[i].weightValue == maxValue){
            [maxWeightedValue addObject:roomMap[i]];
        }
    }
    return maxWeightedValue;
}

@end
